/*
* SISTEMAS OPERATIVOS II - TP1: SOCKETS (Server)
* Alumno: Cipolatti Mauro
* Matrícula: 35990189
* 
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/stat.h>
#define TAM 256

int main(int argc, char *argv[])
{
	char buffer[TAM]; 													//Buffer por donde se da la comunicación.
	char temp[TAM];														//Temp se utilizara para trabajar. Cada vez q se recibe un mensaje, se copia
																		//buffer a temp para usar temp y no perder el mensaje original.
	char *parseado;														//Se utiliza en el put para poder recibir un archivo de cualquier lugar.
	
	int puertoUDP = 6210;
	int puerto = 6211;
		        
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);						//Creamos el socket.Tipo Stream, Inet.
    if (sockfd < 0){perror("ERROR abriendo el Socket TCP");exit(1);}
    int sockfdUDP = socket( AF_INET, SOCK_DGRAM, 0 );
	if (sockfdUDP < 0) {perror("ERROR abriendo el Socket UDP");exit(1);}
    
    
    //Ligamos el socket a una dirección.
    struct sockaddr_in serv_addr; 
    memset( (char *) &serv_addr, 0, sizeof(serv_addr) ); 				//Limpiamos el struct
	serv_addr.sin_family = AF_INET;						 				//Definimos la familia
	serv_addr.sin_addr.s_addr = INADDR_ANY;				 				//La dirección para el server es la local (INADDS_ANY)
	serv_addr.sin_port = htons( puerto );				 				//Especificamos el puerto, poniendo el numero en el orden correcto.
    int n = bind(sockfd, ( struct sockaddr *) &serv_addr, sizeof( serv_addr ) );//Ligamos
    if(n<0){perror( "ERROR en ligadura TCP" );exit(1);}
    printf( "Proceso: %d - socket disponible: %d\n", getpid(), ntohs(serv_addr.sin_port) );
	//El socket está listo. Hacemos lo mismo con el UDP

	struct sockaddr_in serv_addrUDP;									//Configuración Socket UDP
	memset((char *) &serv_addrUDP, 0, sizeof(serv_addrUDP) );
	serv_addrUDP.sin_family = AF_INET;
	serv_addrUDP.sin_addr.s_addr = INADDR_ANY;
	serv_addrUDP.sin_port = htons( puertoUDP );
	memset( &(serv_addrUDP.sin_zero), '\0', 8 );
	n=bind( sockfdUDP, (struct sockaddr *) &serv_addrUDP, sizeof(serv_addrUDP) );
	if(n<0 ) {perror( "ERROR en ligadura UDP" );exit(1);}

	//Escuchamos en el TCP
	listen( sockfd, 5 );
	struct sockaddr_in cli_addr;
	uint clilen = sizeof(cli_addr);
	
	while(1){															//While del proceso padre. Para aceptar distintas conexiones.
	int newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); //Accept bloquea el proceso hasta que un cliente intenete conectarse al socket.
    if (newsockfd < 0){perror("ERROR on accept"); exit(1);}

	int pid=fork();														// Cuando se conecta el cliente, es atendido por un hijo.
	if(pid < 0){perror("ERROR en fork");exit(1);}
	if(pid == 0)														//Proceso Hijo.
	{
		close( sockfd );										
		char buftecla[TAM];												//Utilizado para el proceso tecla por tecla.
		int i;															//Utilizado para el proceso tecla por tecla.
		
		while(1)														//While infinito de recepción de comandos.
		{
		char *segundoarg = temp;										//Apuntamos segundoarg a temp para parsear mas adelante
		memset(buffer,0,TAM);											//Llenamos de ceros el buffer
		printf("Esperando...\n");
		printf("Mensaje: ");
		fflush(stdout);													
		

		n = read(newsockfd,buftecla,TAM-1);								//De nuevo el proceso queda a la espera, de que haya algo que leer en el buffer.Tomamos la primer tecla.
		if (n < 0){perror("ERROR escribiendo al socket (client)");exit(1);}
			
		while( strcmp( "msgend", buftecla ))                            //Se recibe "msgend" cuando el usuario presione enter en el cliente.
        {																//Es decir que mientras no recibamos msgend, no salimos del while.
         if(!strcmp( "borra1;", buftecla ))                             //Se recibe "borra1;" cuando el usuario presiono backspace en el cliente.
         {
          										
          if(i!=0)														//Se realiza un control con i para saber la posición de la cadena en la que estamos.                                                      
          {																//Si estamos al principio (i=0) no realizamos nada. Sino, debemos borrar el último.
           buffer[strlen(buffer)-1] = '\0';                             
           printf("\b \b");
           fflush(stdout);                                             
           i--;                                                        	//Borramos el último caracter, retrocedemos en la cadena.
		  }																
         }
         else
         {																//El usuario no borro, debemos concatenar al final.
         strcat(buffer,buftecla);
          i++;															//Avanzamos una posición en la cadena.
          printf("%s",buftecla);
          fflush(stdout);
          
         }
		memset(buftecla,0,TAM);											//Llenamos de ceros el buffer  
        n = read(newsockfd,buftecla,TAM-1);								//De nuevo el proceso queda a la espera, de que haya algo que leer en el buffer.Tomamos la tecla.
	    if (n < 0){perror("ERROR leyendo del socket (client)");exit(1);}
        }
		memset(buftecla,0,TAM);
		printf("\n");													//Imprimimos un enter simulando el que ingresó el usuario en el cliente.
		buffer[strlen(buffer)+1]='\0';									//Agregamos el '\0' para utilizar funciones de string.h
		
		strcpy(temp,buffer);											//Copiamos a temp el comando, para trabajar y no perder el original.
		
		if( !strcmp( "bye", temp ) ) 									//Chequeamos si recibió bye para terminar el proceso hijo q atendia esta conexión
		 {
		printf( "PROCESO %d. Recibi 'bye'. Fin de la ejecucion.\n", getpid() );
		exit(0);
		 }
		else{
		strcpy(temp,buffer);
		if( !strcmp( "cd", strtok(temp," ")) )							//Chequeamos si el comando era cd para hacer el parseo y usar chdir.
		{																//Ya que cd no funciona con system().
		 segundoarg = strtok (NULL, " ");
		 n = chdir(segundoarg);											//En segundoarg tengo el directorio destino.
		 if(n<0){ perror("ERROR en cambio de directorio");	 			//Informe de error en cambio de directorio tanto en server como cliente.
		 n = write(newsockfd,"ERROR en cambio de directorio\n",30);		
		 if (n < 0) {perror("ERROR escribiendo al socket (server)");exit(1);}}
		 
		}
		else{
		strcpy(temp,buffer);
		
		int size;														//Size es el tamaño de los archivos.Se usa para control.
		if(!strcmp( "get", strtok(temp," "))) 							//Chequeamos si es get
		{
			int ctrl_arch=0;											//Se utilizara una flag para el archivo solicitado
			int tamano_direccion = sizeof( struct sockaddr );
			segundoarg = strtok (NULL, " ");							//Obtenemos la ruta o nombre del archivo
			FILE *FilePointer=fopen(segundoarg,"rb");					//Lo abrimos en modo binario.
			if(FilePointer==NULL){ctrl_arch=1;perror("ERROR abriendo archivo");}
			
		    n = recvfrom( sockfdUDP, buffer, TAM-1, 0, (struct sockaddr *)&serv_addrUDP,  (socklen_t*)&tamano_direccion );//Escuchamos la 1° conexion del cliente.
			if(n<0){perror("ERROR en lectura de socket");exit(1);}
			
			if(ctrl_arch==0){											//Tenemos que enviar el tam del archivo o ERROR.
																		//Si se pudo abrir el archivo, buscamos el tamaño. Sino mandamos ERROR.
			struct stat st;												//4 lineas para conocer el tamaño del archivo.
			stat(segundoarg, &st);										
			size = st.st_size;
			rewind(FilePointer);
			sprintf(buffer,"%d",size);									//Copiamos el tamaño en el buffer
												
			n = sendto( sockfdUDP, buffer, TAM-1, 0, (struct sockaddr *)&serv_addrUDP,  tamano_direccion );//Enviamos el tamaño.
			if(n<0){perror("ERROR en escritura de socket");exit(1);}
						
			int enviados=0,leidos=0;									//Se utiliza para el envío controlado.
			while(enviados!=size){
			memset( buffer, 0, TAM );					
			leidos=fread(buffer,1,TAM,FilePointer);						//Leemos el archivo y guardamos la cant. de bytes leidos en leidos.
			n=sendto( sockfdUDP,(void *) buffer, leidos, 0, (struct sockaddr *)&serv_addrUDP,  tamano_direccion );//Se envian los "leidos" bytes.
			if(n<0){perror("ERROR en escritura de socket");exit(1);}
			enviados=enviados+leidos;									//Vamos acumulando los bytes enviados para saber cuando cortar.
			}
			
			if(fclose(FilePointer)!=0){perror("ERROR cerrando el archivo");}//Una vez enviado, cerramos el archivo.
		}
		else  															//Else del control de error en apertura de archivo.
		{strcpy(buffer,"ERROR");	
		 n = sendto( sockfdUDP, buffer, TAM-1, 0, (struct sockaddr *)&serv_addrUDP,  tamano_direccion );//Enviamos ERROR.
		 if(n<0){perror("ERROR en escritura de socket");exit(1);}
		}}
		else{															//Else de get. Seguimos viendo si es put o cualquier comando.	
		strcpy(temp,buffer);
			
		if(!strcmp( "put", strtok(temp," "))) 							//Chequeamos si es put.
		{
			int tamano_direccion = sizeof( struct sockaddr );
			segundoarg=temp;											//Vamos a parsear para poder hacer put de cualquier archivo.
	   
			while(segundoarg != NULL)
			{
			parseado=segundoarg;   
			segundoarg = strtok (NULL, "/");							
	   		}
			FILE *FilePointer=fopen(parseado,"w+b");					//Lo abrimos en modo binario.
						
			n = recvfrom( sockfdUDP, buffer, TAM-1, 0, (struct sockaddr *)&serv_addrUDP,  (socklen_t*)&tamano_direccion );//Escuchamos la 1° conexion del cliente.
			if(n<0){perror("ERROR en lectura de socket");exit(1);}
			if(!strcmp( "ERROR", strtok(buffer," "))) 					//Chequeamos si el cliente nos devolvio error en el archivo o no.
			{printf("ERROR en el archivo requerido.\n");
			 remove(parseado);}											//Hubo error. Imprimimos mensaje y borramos el archivo abierto listo para recibir.
			else
			{
			size=atoi(buffer);											//No hubo error. Tomamos el tamaño y lo guardamos en size.
			int recibidos=0;
			while(recibidos!=size){
			memset( buffer, 0, TAM );					
			n=recvfrom( sockfdUDP,(void *) buffer, TAM, 0, (struct sockaddr *)&serv_addrUDP,  (socklen_t*)&tamano_direccion);//Recibimos n bytes.
			if(n<0){perror("ERROR en lectura de socket");exit(1);}
			fwrite(buffer,1,n,FilePointer);								//Escribimos los n bytes en el archivo.
			recibidos=recibidos+n;										//Vamos acumulando los bytes recibidos para saber cuando cortar.
			}
			
			if(fclose(FilePointer)!=0){perror("ERROR cerrando el archivo");}//Una vez recibido, cerramos el archivo.	
			}
			}  
		else       														//Es un comando cualquiera
		{
		int stdoutCopy = dup(1);                						//Copiamos stdout a un nuevo descriptor
		dup2(newsockfd, 1);			              						//Cambiamos stdout al socket
		                
		int resultado = system(buffer);									//Ejecutamos el comando
		
		dup2(stdoutCopy,1);		   										//Volvemos del Socket a Stdout
		   
		if(resultado!=0){ perror("ERROR en ejecución");					//Si hubo error en la ejecución, tenemos que informar para el logger del cliente.
		 n = write(newsockfd,"ERROR en ejecucion\n",19);		
		 if (n < 0) {perror("ERROR escribiendo al socket (server)");exit(1);}
		}
		}}}}
		
		memset( buffer, 0, TAM );		
		n = write(newsockfd,"Recepción OK",13);							//Informamos de la recepción OK al cliente
		if (n < 0) {perror("ERROR escribiendo al socket (server)");exit(1);}
		
		}//Fin del while
    }//if de pid==0
    else
    {
    printf( "SERVIDOR: Nuevo cliente, atendido por el proceso hijo: %d\n", pid );
	close( newsockfd );
    }
 }
    return 0; 
}
